/**
 * 
 */
package org.swing.utility.common.string;

import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.Properties;
import java.util.StringTokenizer;

import org.swing.utility.common.exception.SubstitutionException;
import org.swing.utility.common.hex.HexUtil;

/**
 * @author lqnhu
 *
 */
public class StringUtil {
	private static final String PRINTABLE = ": ~`!@#$%^&*()-_+=/\\,.[]{}|?<>\"'";
	private static final String SAFE = ":~!@#$%^&*()-_+=/\\,.[]{}|?<>";
	// (gt, lt, quot, amp, apos) and then newline, carriage return
	private static final String[][] XML_CHARS = { { "&", "&amp;" },
			{ "<", "&lt;" }, { ">", "&gt;" }, { "\"", "&quot;" },
			{ "'", "&apos;" }, { "\n", "&#10;" }, { "\r", "&#13;" }, };

	/**
	 * Searches the string for occurrences of the pattern $ENV{key} and attempts
	 * to replace this pattern with a value from the System environment obtained
	 * using the 'key'. For example, including "$ENV{USERNAME}" in a string and
	 * calling this method would then attempt to replace the entire pattern with
	 * the value of the environment variable "USERNAME". The System environment
	 * is obtained in Java with a call to System.getenv(). An environment
	 * variable is typically defined in the Linux shell or Windows property
	 * tabs. NOTE: A Java System property is not the same as an environment
	 * variable.
	 * 
	 * @param string0
	 *            The string to perform substitution on such as
	 *            "Hello $ENV{USERNAME}". This string may be null, empty, or
	 *            contain one or more substitutions.
	 * @return A string with all occurrences of keys substituted with their
	 *         values obtained from the System environment. Can be null if the
	 *         original string was null.
	 * @throws SubstitutionException
	 *             Thrown if a starting string was found, but the ending string
	 *             was not. Also, thrown if a key value was empty such as using
	 *             "$ENV{}". Finally, thrown if the property key was not found
	 *             in the properties object (could not be replaced).
	 * @see #substituteWithProperties(java.lang.String, java.lang.String,
	 *      java.lang.String, java.util.Properties)
	 */
	public static String substituteWithEnvironment(String string0)
			throws SubstitutionException {
		// turn environment into properties
		Properties envProps = new Properties();
		// add all system environment vars to the properties
		envProps.putAll(System.getenv());
		// delegate to other method using the default syntax $ENV{<key>}
		return substituteWithProperties(string0, "$ENV{", "}", envProps);
	}

	/**
	 * Searches string for occurrences of a pattern, extracts out a key name
	 * between the startStr and endStr tokens, then attempts to replace the
	 * property value into the string. This method is useful for merging
	 * property values into configuration strings/settings. For examle, the
	 * system environment Map could be converted into a Properties object then
	 * have its values merged into a String so that users can access environment
	 * variables.
	 * 
	 * @param string0
	 *            The string to perform substitution on such as
	 *            "Hello $ENV{TEST}". This string may be null, empty, or contain
	 *            one or more substitutions.
	 * @param startStr
	 *            The string that marks the start of a replacement key such as
	 *            "$ENV{" if the final search pattern you wanted was
	 *            "$ENV{<key>}".
	 * @param endStr
	 *            The string that marks the end of a replacement key such as "}"
	 *            if the final search pattern you wanted was "$ENV{<key>}".
	 * @param properties
	 *            The property keys and associated values to use for
	 *            replacement.
	 * @return A string with all occurrences of keys substituted with their
	 *         values obtained from the properties object. Can be null if the
	 *         original string was null.
	 * @throws SubstitutionException
	 *             Thrown if a starting string was found, but the ending string
	 *             was not. Also, thrown if a key value was empty such as using
	 *             "$ENV{}". Finally, thrown if the property key was not found
	 *             in the properties object (could not be replaced).
	 * @see #substituteWithEnvironment(java.lang.String)
	 */
	public static String substituteWithProperties(String string0,
			String startStr, String endStr, Properties properties)
			throws SubstitutionException {
		// a null source string will always return the same -- a null result
		if (string0 == null) {
			return null;
		}
		// create a builder for the resulting string
		StringBuilder result = new StringBuilder(string0.length());
		// attempt to find the first occurrence of the starting string
		int end = -1;
		int pos = string0.indexOf(startStr);
		// keep looping while we keep finding more occurrences
		while (pos >= 0) {
			// is there string data before the position that we should append to
			// the result?
			if (pos > end + 1) {
				result.append(string0.substring(end + endStr.length(), pos));
			}
			// search for endStr starting from the end of the startStr
			end = string0.indexOf(endStr, pos + startStr.length());
			// was the end found?
			if (end < 0) {
				throw new SubstitutionException("End of substitution pattern '"
						+ endStr + "' not found [@position=" + pos + "]");
			}
			// extract the part in the middle of the start and end strings
			String key = string0.substring(pos + startStr.length(), end);
			// NOTE: don't trim the key, whitespace technically matters...
			// was there anything left?
			if (key == null || key.equals("")) {
				throw new SubstitutionException(
						"Property key was empty in string with an occurrence of '"
								+ startStr + endStr + "' [@position=" + pos
								+ "]");
			}
			// attempt to get this property
			String value = properties.getProperty(key);
			// was the property found
			if (value == null) {
				throw new SubstitutionException("A property value for '"
						+ startStr + key + endStr
						+ "' was not found (property missing?)");
			}
			// append this value to our result
			result.append(value);
			// find next occurrence after last end
			pos = string0.indexOf(startStr, end + endStr.length());
		}
		// is there any string data we missed in the loop above?
		if (end + endStr.length() < string0.length()) {
			// append the remaining part of the string
			result.append(string0.substring(end + endStr.length()));
		}
		return result.toString();
	}

	/**
	 * Returns true if the String is considered a "safe" string where only
	 * specific characters are allowed to be used. Useful for checking passwords
	 * or other information you don't want a user to be able to type just
	 * anything in. This method does not allow any whitespace characters,
	 * newlines, carriage returns. Primarily allows [a-z] [A-Z] [0-9] and a few
	 * other useful ASCII characters such as ":~!@#$%^*()-_+=/\\,.[]{}|?<>" (but
	 * not the quote chars)
	 */
	public static boolean isSafeString(String string0) {
		for (int i = 0; i < string0.length(); i++) {
			if (!isSafeChar(string0.charAt(i))) {
				return false;
			}
		}
		return true;
	}

	/**
	 * Returns true if the char is considered a "safe" char. Please see
	 * documentation for isSafeString().
	 * 
	 * @see #isSafeString(java.lang.String)
	 */
	public static boolean isSafeChar(char ch) {
		if (ch >= 'a' && ch <= 'z')
			return true;
		if (ch >= 'A' && ch <= 'Z')
			return true;
		if (ch >= '0' && ch <= '9')
			return true;
		// loop thru our PRINTABLE string
		for (int i = 0; i < SAFE.length(); i++) {
			if (ch == SAFE.charAt(i))
				return true;
		}
		return false;
	}

	/**
	 * Safely capitalizes a string by converting the first character to upper
	 * case. Handles null, empty, and strings of length of 1 or greater. For
	 * example, this will convert "joe" to "Joe". If the string is null, this
	 * will return null. If the string is empty such as "", then it'll just
	 * return an empty string such as "".
	 * 
	 * @param string0
	 *            The string to capitalize
	 * @return A new string with the first character converted to upper case
	 */
	static public String capitalize(String string0) {
		if (string0 == null) {
			return null;
		}
		int length = string0.length();
		// if empty string, just return it
		if (length == 0) {
			return string0;
		} else if (length == 1) {
			return string0.toUpperCase();
		} else {
			StringBuilder buf = new StringBuilder(length);
			buf.append(string0.substring(0, 1).toUpperCase());
			buf.append(string0.substring(1));
			return buf.toString();
		}
	}

	/**
	 * Safely uncapitalizes a string by converting the first character to lower
	 * case. Handles null, empty, and strings of length of 1 or greater. For
	 * example, this will convert "Joe" to "joe". If the string is null, this
	 * will return null. If the string is empty such as "", then it'll just
	 * return an empty string such as "".
	 * 
	 * @param string0
	 *            The string to uncapitalize
	 * @return A new string with the first character converted to lower case
	 */
	static public String uncapitalize(String string0) {
		if (string0 == null) {
			return null;
		}
		int length = string0.length();
		// if empty string, just return it
		if (length == 0) {
			return string0;
		} else if (length == 1) {
			return string0.toLowerCase();
		} else {
			StringBuilder buf = new StringBuilder(length);
			buf.append(string0.substring(0, 1).toLowerCase());
			buf.append(string0.substring(1));
			return buf.toString();
		}
	}

	/**
	 * Checks if the targetString is contained within the array of strings. This
	 * method will return true if a "null" is contained in the array and the
	 * targetString is also null.
	 * 
	 * @param strings
	 *            The array of strings to search.
	 * @param targetString
	 *            The string to search for
	 * @return True if the string is contained within, otherwise false. Also
	 *         returns false if the strings array is null.
	 */
	static public boolean contains(String[] strings, String targetString) {
		return (indexOf(strings, targetString) != -1);
	}

	/**
	 * Finds the first occurrence of the targetString in the array of strings.
	 * Returns -1 if an occurrence wasn't found. This method will return true if
	 * a "null" is contained in the array and the targetString is also null.
	 * 
	 * @param strings
	 *            The array of strings to search.
	 * @param targetString
	 *            The string to search for
	 * @return The index of the first occurrence, or -1 if not found. If strings
	 *         array is null, will return -1;
	 */
	static public int indexOf(String[] strings, String targetString) {
		if (strings == null)
			return -1;
		for (int i = 0; i < strings.length; i++) {
			if (strings[i] == null) {
				if (targetString == null) {
					return i;
				}
			} else {
				if (targetString != null) {
					if (strings[i].equals(targetString)) {
						return i;
					}
				}
			}
		}
		return -1;
	}

	/**
	 * If present, this method will strip off the leading and trailing "
	 * character in the string parameter. For example, "10958" will becomes just
	 * 10958.
	 */
	static public String stripQuotes(String string0) {
		// if an empty string, return it
		if (string0.length() == 0) {
			return string0;
		}
		// if the first and last characters are quotes, just do 1 substring
		if (string0.length() > 1 && string0.charAt(0) == '"'
				&& string0.charAt(string0.length() - 1) == '"') {
			return string0.substring(1, string0.length() - 1);
		} else if (string0.charAt(0) == '"') {
			string0 = string0.substring(1);
		} else if (string0.charAt(string0.length() - 1) == '"') {
			string0 = string0.substring(0, string0.length() - 1);
		}
		return string0;
	}

	/**
	 * Checks if a string contains only digits.
	 * 
	 * @return True if the string0 only contains digits, or false otherwise.
	 */
	static public boolean containsOnlyDigits(String string0) {
		// are they all digits?
		for (int i = 0; i < string0.length(); i++) {
			if (!Character.isDigit(string0.charAt(i))) {
				return false;
			}
		}
		return true;
	}

	/**
	 * Splits a string around matches of the given delimiter character.
	 *
	 * Where applicable, this method can be used as a substitute for
	 * <code>String.split(String regex)</code>, which is not available on a
	 * JSR169/Java ME platform.
	 *
	 * @param str
	 *            the string to be split
	 * @param delim
	 *            the delimiter
	 * @throws NullPointerException
	 *             if str is null
	 */
	static public String[] split(String str, char delim) {
		if (str == null) {
			throw new NullPointerException("str can't be null");
		}
		// Note the javadoc on StringTokenizer:
		// StringTokenizer is a legacy class that is retained for
		// compatibility reasons although its use is discouraged in
		// new code.
		// In other words, if StringTokenizer is ever removed from the JDK,
		// we need to have a look at String.split() (or java.util.regex)
		// if it is supported on a JSR169/Java ME platform by then.
		StringTokenizer st = new StringTokenizer(str, String.valueOf(delim));
		int n = st.countTokens();
		String[] s = new String[n];
		for (int i = 0; i < n; i++) {
			s[i] = st.nextToken();
		}
		return s;
	}

	/**
	 * Used to print out a string for error messages, chops is off at 60 chars
	 * for historical reasons.
	 */
	public final static String formatForPrint(String input) {
		if (input.length() > 60) {
			StringBuffer tmp = new StringBuffer(input.substring(0, 60));
			tmp.append("&");
			input = tmp.toString();
		}
		return input;
	}

	/**
	 * A method that receive an array of Objects and return a String array
	 * representation of that array.
	 */
	public static String[] toStringArray(Object[] objArray) {
		int idx;
		int len = objArray.length;
		String[] strArray = new String[len];
		for (idx = 0; idx < len; idx++) {
			strArray[idx] = objArray[idx].toString();
		}
		return strArray;
	}

	/**
	 * Get 7-bit ASCII character array from input String. The lower 7 bits of
	 * each character in the input string is assumed to be the ASCII character
	 * value. Hexadecimal - Character | 00 NUL| 01 SOH| 02 STX| 03 ETX| 04 EOT|
	 * 05 ENQ| 06 ACK| 07 BEL| | 08 BS | 09 HT | 0A NL | 0B VT | 0C NP | 0D CR |
	 * 0E SO | 0F SI | | 10 DLE| 11 DC1| 12 DC2| 13 DC3| 14 DC4| 15 NAK| 16 SYN|
	 * 17 ETB| | 18 CAN| 19 EM | 1A SUB| 1B ESC| 1C FS | 1D GS | 1E RS | 1F US |
	 * | 20 SP | 21 ! | 22 " | 23 # | 24 $ | 25 % | 26 & | 27 ' | | 28 ( | 29 )
	 * | 2A * | 2B + | 2C , | 2D - | 2E . | 2F / | | 30 0 | 31 1 | 32 2 | 33 3 |
	 * 34 4 | 35 5 | 36 6 | 37 7 | | 38 8 | 39 9 | 3A : | 3B ; | 3C < | 3D = |
	 * 3E > | 3F ? | | 40 @ | 41 A | 42 B | 43 C | 44 D | 45 E | 46 F | 47 G | |
	 * 48 H | 49 I | 4A J | 4B K | 4C L | 4D M | 4E N | 4F O | | 50 P | 51 Q |
	 * 52 R | 53 S | 54 T | 55 U | 56 V | 57 W | | 58 X | 59 Y | 5A Z | 5B [ |
	 * 5C \ | 5D ] | 5E ^ | 5F _ | | 60 ` | 61 a | 62 b | 63 c | 64 d | 65 e |
	 * 66 f | 67 g | | 68 h | 69 i | 6A j | 6B k | 6C l | 6D m | 6E n | 6F o | |
	 * 70 p | 71 q | 72 r | 73 s | 74 t | 75 u | 76 v | 77 w | | 78 x | 79 y |
	 * 7A z | 7B { | 7C | | 7D } | 7E ~ | 7F DEL|
	 */
	public static byte[] getAsciiBytes(String input) {
		char[] c = input.toCharArray();
		byte[] b = new byte[c.length];
		for (int i = 0; i < c.length; i++) {
			b[i] = (byte) (c[i] & 0x007F);
		}
		return b;
	}

	public static String getAsciiString(byte[] input) {
		StringBuffer buf = new StringBuffer(input.length);
		for (byte b : input) {
			buf.append((char) b);
		}
		return buf.toString();
	}

	/**
	 * Trim off trailing blanks but not leading blanks
	 * 
	 * @param str
	 * @return The input with trailing blanks stipped off
	 */
	public static String trimTrailing(String str) {
		if (str == null) {
			return null;
		}
		int len = str.length();
		for (; len > 0; len--) {
			if (!Character.isWhitespace(str.charAt(len - 1))) {
				break;
			}
		}
		return str.substring(0, len);
	}

	/**
	 * Truncate a String to the given length with no warnings or error raised if
	 * it is bigger.
	 * 
	 * @param value
	 *            String to be truncated
	 * @param length
	 *            Maximum length of string
	 * @return Returns value if value is null or value.length() is less or equal
	 *         to than length, otherwise a String representing value truncated
	 *         to length.
	 */
	public static String truncate(String value, int length) {
		if (value != null && value.length() > length) {
			value = value.substring(0, length);
		}
		return value;
	}

	/**
	 * Return a slice (substring) of the passed in value, optionally trimmed.
	 * WARNING - endOffset is inclusive for historical reasons, unlike
	 * String.substring() which has an exclusive ending offset.
	 * 
	 * @param value
	 *            Value to slice, must be non-null.
	 * @param beginOffset
	 *            Inclusive start character
	 * @param endOffset
	 *            Inclusive end character
	 * @param trim
	 *            To trim or not to trim
	 * @return Sliceed value.
	 */
	public static String slice(String value, int beginOffset, int endOffset,
			boolean trim) {
		String retval = value.substring(beginOffset, endOffset + 1);
		if (trim) {
			retval = retval.trim();
		}
		return retval;
	}

	public static char[] HEX_TABLE = { '0', '1', '2', '3', '4', '5', '6', '7',
			'8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };

	/**
	 * @deprecated Please use new utility class HexUtil
	 * @see HexUtil#toHexString(byte[])
	 */
	@Deprecated
	public static String toHexString(byte[] bytes) {
		return HexUtil.toHexString(bytes);
	}

	/**
	 * @deprecated Please use new utility class HexUtil
	 * @see HexUtil#toHexString(byte[], int, int)
	 */
	@Deprecated
	public static String toHexString(byte[] bytes, int offset, int length) {
		return HexUtil.toHexString(bytes, offset, length);
	}

	/**
	 * @deprecated Please use new utility class HexUtil
	 * @see HexUtil#toByteArray(java.lang.CharSequence, int, int)
	 */
	@Deprecated
	public static byte[] toHexByte(String hexString, int offset, int length) {
		return HexUtil.toByteArray(hexString, offset, length);
	}

	/**
	 * Converts from a hex string like "FF" to a byte[].
	 * 
	 * @param string0
	 *            The string containing the hex-encoded data.
	 * @return
	 * @deprecated Please use new utility class HexUtil
	 * @see HexUtil#toByteArray(java.lang.CharSequence)
	 */
	@Deprecated
	public static byte[] fromHexString(String hexString) {
		return HexUtil.toByteArray(hexString);
	}

	/**
	 * Convert a hexidecimal string generated by toHexString() back into a byte
	 * array.
	 * 
	 * @param s
	 *            String to convert
	 * @param offset
	 *            starting character (zero based) to convert.
	 * @param length
	 *            number of characters to convert.
	 * @return the converted byte array. Returns null if the length is not a
	 *         multiple of 2.
	 * @deprecated Please use new utility class HexUtil
	 * @see HexUtil#toByteArray(java.lang.CharSequence, int, int)
	 */
	@Deprecated
	public static byte[] fromHexString(String hexString, int offset, int length) {
		return HexUtil.toByteArray(hexString, offset, length);
	}

	/**
	 * Return true if the character is printable in ASCII. Not using
	 * Character.isLetterOrDigit(); applies to all unicode ranges.
	 */
	public static boolean isPrintableChar(char ch) {
		if (ch >= 'a' && ch <= 'z') {
			return true;
		}
		if (ch >= 'A' && ch <= 'Z') {
			return true;
		}
		if (ch >= '0' && ch <= '9') {
			return true;
		}
		// loop thru our PRINTABLE string
		for (int i = 0; i < PRINTABLE.length(); i++) {
			if (ch == PRINTABLE.charAt(i)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Convert a byte array to a human-readable String for debugging purposes.
	 */
	public static String hexDump(String prefix, byte[] data) {
		byte byte_value;
		StringBuffer str = new StringBuffer(data.length * 3);
		str.append(prefix);
		for (int i = 0; i < data.length; i += 16) {
			// dump the header: 00000000:
			String offset = Integer.toHexString(i);
			// "0" left pad offset field so it is always 8 char's long.
			str.append(" ");
			for (int offlen = offset.length(); offlen < 8; offlen++) {
				str.append("0");
			}
			str.append(offset);
			str.append(":");
			// dump hex version of 16 bytes per line.
			for (int j = 0; (j < 16) && ((i + j) < data.length); j++) {
				byte_value = data[i + j];
				// add spaces between every 2 bytes.
				if ((j % 2) == 0) {
					str.append(" ");
				}
				// dump a single byte.
				byte high_nibble = (byte) ((byte_value & 0xf0) >>> 4);
				byte low_nibble = (byte) (byte_value & 0x0f);
				str.append(HEX_TABLE[high_nibble]);
				str.append(HEX_TABLE[low_nibble]);
			}
			// IF THIS IS THE LAST LINE OF HEX, THEN ADD THIS
			if (i + 16 > data.length) {
				// for debugging purposes, I want the last bytes always padded
				// over so that the ascii portion is correctly positioned
				int last_row_byte_count = data.length % 16;
				int num_bytes_short = 16 - last_row_byte_count;
				// number of spaces to add = (num bytes remaining * 2 spaces per
				// byte) + (7 - (num bytes % 2))
				int num_spaces = (num_bytes_short * 2)
						+ (7 - (last_row_byte_count / 2));
				for (int v = 0; v < num_spaces; v++) {
					str.append(" ");
				}
			}
			// dump ascii version of 16 bytes
			str.append(" ");
			for (int j = 0; (j < 16) && ((i + j) < data.length); j++) {
				char char_value = (char) data[i + j];
				// RESOLVE (really want isAscii() or isPrintable())
				// if (Character.isLetterOrDigit(char_value))
				if (isPrintableChar(char_value)) {
					str.append(String.valueOf(char_value));
				} else {
					str.append(".");
				}
			}
			// new line
			str.append("\n");
		}
		// always trim off the last newline
		str.deleteCharAt(str.length() - 1);
		return (str.toString());
	}

	// The functions below are used for uppercasing SQL in a consistent manner.
	// Derby will uppercase Turkish to the English locale to avoid i
	// uppercasing to an uppercase dotted i. In future versions, all
	// casing will be done in English. The result will be that we will get
	// only the 1:1 mappings in
	// http://www.unicode.org/Public/3.0-Update1/UnicodeData-3.0.1.txt
	// and avoid the 1:n mappings in
	// http://www.unicode.org/Public/3.0-Update1/SpecialCasing-3.txt
	//
	// Any SQL casing should use these functions
	/**
	 * Convert string to uppercase Always use the java.util.ENGLISH locale
	 * 
	 * @param s
	 *            string to uppercase
	 * @return uppercased string
	 */
	public static String SQLToUpperCase(String s) {
		return s.toUpperCase(Locale.ENGLISH);
	}

	/**
	 * Compares two strings Strings will be uppercased in english and compared
	 * equivalent to s1.equalsIgnoreCase(s2) throws NPE if s1 is null
	 *
	 * @param s1
	 *            first string to compare
	 * @param s2
	 *            second string to compare
	 *
	 * @return true if the two upppercased ENGLISH values are equal return false
	 *         if s2 is null
	 */
	public static boolean SQLEqualsIgnoreCase(String s1, String s2) {
		if (s2 == null) {
			return false;
		} else {
			return SQLToUpperCase(s1).equals(SQLToUpperCase(s2));
		}
	}

	/**
	 * Normalize a SQL identifer, up-casing if <regular identifer>, and handling
	 * of <delimited identifer> (SQL 2003, section 5.2). The normal form is used
	 * internally in Derby.
	 *
	 * @param id
	 *            syntacically correct SQL identifier
	 */
	public static String normalizeSQLIdentifier(String id) {
		if (id.length() == 0) {
			return id;
		}
		if (id.charAt(0) == '"' && id.length() >= 3
				&& id.charAt(id.length() - 1) == '"') {
			// assume syntax is OK, thats is, any quotes inside are doubled:
			return StringUtil.compressQuotes(id.substring(1, id.length() - 1),
					"\"\"");
		} else {
			return StringUtil.SQLToUpperCase(id);
		}
	}

	/**
	 * Compress 2 adjacent (single or double) quotes into a single (s or d)
	 * quote when found in the middle of a String.
	 *
	 * NOTE: """" or '''' will be compressed into "" or ''. This function
	 * assumes that the leading and trailing quote from a string or delimited
	 * identifier have already been removed.
	 * 
	 * @param source
	 *            string to be compressed
	 * @param quotes
	 *            string containing two single or double quotes.
	 * @return String where quotes have been compressed
	 */
	public static String compressQuotes(String source, String quotes) {
		String result = source;
		int index;
		/* Find the first occurrence of adjacent quotes. */
		index = result.indexOf(quotes);
		/*
		 * Replace each occurrence with a single quote and begin the search for
		 * the next occurrence from where we left off.
		 */
		while (index != -1) {
			result = result.substring(0, index + 1)
					+ result.substring(index + 2);
			index = result.indexOf(quotes, index + 1);
		}
		return result;
	}

	/**
	 * Quote a string so that it can be used as an identifier or a string
	 * literal in SQL statements. Identifiers are surrounded by double quotes
	 * and string literals are surrounded by single quotes. If the string
	 * contains quote characters, they are escaped.
	 *
	 * @param source
	 *            the string to quote
	 * @param quote
	 *            the character to quote the string with (' or &quot;)
	 * @return a string quoted with the specified quote character
	 * @see #quoteStringLiteral(String)
	 * @see IdUtil#normalToDelimited(String)
	 */
	static String quoteString(String source, char quote) {
		// Normally, the quoted string is two characters longer than the source
		// string (because of start quote and end quote).
		StringBuffer quoted = new StringBuffer(source.length() + 2);
		quoted.append(quote);
		for (int i = 0; i < source.length(); i++) {
			char c = source.charAt(i);
			// if the character is a quote, escape it with an extra quote
			if (c == quote)
				quoted.append(quote);
			quoted.append(c);
		}
		quoted.append(quote);
		return quoted.toString();
	}

	/**
	 * Quote a string so that it can be used as a string literal in an SQL
	 * statement.
	 *
	 * @param string
	 *            the string to quote
	 * @return the string surrounded by single quotes and with proper escaping
	 *         of any single quotes inside the string
	 */
	public static String quoteStringLiteral(String string) {
		return quoteString(string, '\'');
	}

	/**
	 * Turn an array of ints into a printable string. Returns what's returned in
	 * Java 5 by java.util.Arrays.toString(int[]).
	 */
	public static String stringify(int[] raw) {
		if (raw == null) {
			return "null";
		}
		StringBuffer buffer = new StringBuffer();
		int count = raw.length;
		buffer.append("[ ");
		for (int i = 0; i < count; i++) {
			if (i > 0) {
				buffer.append(", ");
			}
			buffer.append(raw[i]);
		}
		buffer.append(" ]");
		return buffer.toString();
	}

	/**
	 * Checks if the string is an empty value which is true if the string is
	 * null or if the string represents an empty string of "". Please note that
	 * a string with just a space " " would not be considered empty.
	 * 
	 * @param string0
	 *            The string to check
	 * @return True if null or "", otherwise false.
	 */
	public static boolean isEmpty(String string0) {
		if (string0 == null || string0.length() == 0) {
			return true;
		} else {
			return false;
		}
	}

	public static boolean isEqual(String string0, String string1) {
		return isEqual(string0, string1, true);
	}

	/**
	 * Returns the value from calling "toString()" on the object, but is a safe
	 * version that gracefully handles NULL objects by returning a String of "".
	 * 
	 * @param obj
	 *            The object to call toString() on. Safely handles a null
	 *            object.
	 * @return The value from obj.toString() or "" if the object is null.
	 * @see #toStringWithNullAsNull(java.lang.Object)
	 */
	static public String toStringWithNullAsEmpty(Object obj) {
		if (obj == null) {
			return "";
		} else {
			return obj.toString();
		}
	}

	/**
	 * Returns the value from calling "toString()" on the object, but is a safe
	 * version that gracefully handles NULL objects by returning a String of
	 * "<NULL>".
	 * 
	 * @param obj
	 *            The object to call toString() on. Safely handles a null
	 *            object.
	 * @return The value from obj.toString() or "<NULL>" if the object is null.
	 * @see #toStringWithNullAsEmpty(java.lang.Object)
	 */
	static public String toStringWithNullAsReplaced(Object obj) {
		if (obj == null) {
			return "<NULL>";
		} else {
			return obj.toString();
		}
	}

	/**
	 * Returns the value from calling "toString()" on the object, but is a safe
	 * version that gracefully handles NULL objects by returning null (rather
	 * than causing a NullPointerException).
	 * 
	 * @param obj
	 *            The object to call toString() on. Safely handles a null
	 *            object.
	 * @return The value from obj.toString() or null if the object is null.
	 * @see #toStringWithNullAsEmpty(java.lang.Object)
	 */
	static public String toStringWithNullAsNull(Object obj) {
		if (obj == null) {
			return null;
		} else {
			return obj.toString();
		}
	}

	/**
	 * Checks if both strings are equal to each other. Safely handles the case
	 * where either string may be null. The strings are evaluated as equal if
	 * they are both null or if they actually equal each other. One string that
	 * is null while the other one isn't (even if its an empty string) will be
	 * considered as NOT equal. Case sensitive comparisons are optional.
	 * 
	 * @param string0
	 *            The string to compare
	 * @param string1
	 *            The other string to compare with
	 * @param caseSensitive
	 *            If true a case sensitive comparison will be made, otherwise
	 *            equalsIgnoreCase will be used.
	 * @return True if the strings are both null or equal to each other,
	 *         otherwise false.
	 */
	public static boolean isEqual(String string0, String string1,
			boolean caseSensitive) {
		if (string0 == null && string1 == null) {
			return true;
		}
		if (string0 == null && string1 != null) {
			return false;
		}
		if (string0 != null && string1 == null) {
			return false;
		}
		if (caseSensitive) {
			return string0.equals(string1);
		} else {
			return string0.equalsIgnoreCase(string1);
		}
	}

	public static String readToString(InputStream in) throws IOException {
		StringBuilder out = new StringBuilder();
		byte[] b = new byte[4096];
		for (int n; (n = in.read(b)) != -1;) {
			out.append(new String(b, 0, n));
		}
		return out.toString();
	}

	public static <T> String join(final String separator,
			final Collection<T> objs) {
		if (objs.size() == 0) {
			return "";
		}
		boolean notFirst = false;
		final StringBuilder ret = new StringBuilder();
		for (final Object obj : objs) {
			if (notFirst) {
				ret.append(separator);
			}
			ret.append(obj.toString());
			notFirst = true;
		}
		return ret.toString();
	}

	public static <T> String join(final String separator, final T... objs) {
		final List<T> values = Arrays.asList(objs);
		return join(separator, values);
	}

	/**
	 * Escapes the characters in a String using XML entities. For example:
	 * "bread" & "butter'ed" => &quot;bread&quot; &amp;
	 * &quot;butter&apos;ed&quot;
	 *
	 * Supports the five basic XML entities (gt, lt, quot, amp, apos) and also
	 * supports a newline and carriage return character. A newline is escaped to
	 * &#10; and a carriage return to &#13;
	 *
	 * @param value
	 *            The string to escape
	 * @return The escaped String that can be used in an XML document.
	 */
	public static String escapeXml(String value) {
		// null to null
		if (value == null)
			return null;
		// assume the resulting string will be the same
		int len = value.length();
		StringBuilder buf = new StringBuilder(len);
		for (int i = 0; i < len; i++) {
			char c = value.charAt(i);
			boolean entityFound = false;
			// is this a matching entity?
			for (int j = 0; j < XML_CHARS.length; j++) {
				// is this the matching character?
				if (c == XML_CHARS[j][0].charAt(0)) {
					// append the entity
					buf.append(XML_CHARS[j][1]);
					entityFound = true;
				}
			}
			if (!entityFound) {
				buf.append(c);
			}
		}
		return buf.toString();
	}

	public static final String EMPTY_STRING = "";

	public static String asEmptyIfNull(final Object string) {
		return string == null ? EMPTY_STRING : string.toString();
	}

	/**
	 * Removes all other characters from a string except digits. A good way of
	 * cleaing up something like a phone number.
	 * 
	 * @param str0
	 *            The string to clean up
	 * @return A new String that has all characters except digits removed
	 */
	static public String removeAllCharsExceptDigits(String str0) {
		if (str0 == null) {
			return null;
		}
		if (str0.length() == 0) {
			return str0;
		}
		StringBuilder buf = new StringBuilder(str0.length());
		int length = str0.length();
		for (int i = 0; i < length; i++) {
			char c = str0.charAt(i);
			if (Character.isDigit(c)) {
				// append this character to our string
				buf.append(c);
			}
		}
		return buf.toString();
	}
}